"""
qiskit-test.py

This python script is based on the fourier_transform example in
the qiskit-tutorial.
"""

#======================================================================#
#------------------------- Prepare Environment ------------------------#
#======================================================================#

# Checking the version of Python;
# The Quantum Experience currently only supports version 3
import sys
if sys.version_info < (3,0):
    raise Exception("Please use Python version 3 or greater.")
    
# Importing QISKit
import math
import sys
#sys.path.append("/Users/rzamora/qiskit-sdk-py/")
from qiskit import QuantumCircuit, QuantumProgram
import Qconfig

# Import basic plotting tools
from qiskit.tools.visualization import plot_histogram

#======================================================================#
#--------------------------- Input Options ----------------------------#
#======================================================================#

n_qubits = 3
real_run = False

#======================================================================#
#----------------------- Define Helper Functions ----------------------#
#======================================================================#

# Define a function to perform the QFT
def qft(circ, q, n):
    """
    n-qubit QFT on q in circ.
    circ - QISKit Circuit to use
    q    - Quantum register
    n    - number of qubits
    """
    for j in range(n):
        for k in range(j):
            circ.cu1(math.pi/float(2**(j-k)), q[j], q[k])
        circ.h(q[j])

# Define a function to prepare the n_qubit input state
def input_state(circ, q, n):
    """n-qubit input state for QFT that produces output 1."""
    for j in range(n):
        circ.h(q[j])
        circ.u1(math.pi/float(2**(j)), q[j]).inverse()

#======================================================================#
#-------------------------- Quantum Program ---------------------------#
#======================================================================#

# Create QuantumProgram object, and set the APIToken and API url
Q_program = QuantumProgram()
Q_program.set_api(Qconfig.APItoken, Qconfig.config["url"])

# Define quantum (q) and classical (c) registers,
# and create the qft3 circuit
q = Q_program.create_quantum_register("q", n_qubits)
c = Q_program.create_classical_register("c", n_qubits)
qft3 = Q_program.create_circuit("qft3", [q], [c])

# Prepare the intput state (by adding gates to the circuit)
input_state(qft3, q, n_qubits)

# Perform the qft (add a QFT to the circuit)
qft(qft3, q, n_qubits)

# Measure each qubit, storing the result in the classical register
# (add gates to the circuit to do these measurements)
for i in range(n_qubits): qft3.measure(q[i], c[i])

# Print the QASM code to actually execute
# (This QASM code is generated by qiskit)
print("\nQASM Code to be executed by the local_qasm_simulator:\n")
print(qft3.qasm())

# Simulate the execution of the qft3 circuit
simulate = Q_program.execute(["qft3"], backend="local_qasm_simulator", shots=1024)

# Print the result of the simulation
print("Simulation Result:")
print(simulate.get_counts("qft3"))
## Plot a histogram of the results
#plot_histogram(simulate.get_counts("qft3"))

# Determine QASM code needed for 'real' ibmqx4 machine.
# First, get the ibmqx4 coupling map:
ibmqx4_backend = Q_program.get_backend_configuration('ibmqx4')
ibmqx4_coupling = ibmqx4_backend['coupling_map']
# Second, compile the program with ibmqx4_coupling:
qobj=Q_program.compile(["qft3"], backend="local_qasm_simulator", coupling_map=ibmqx4_coupling)
# Last, get the new QASM source, and print it:
QASM_source = Q_program.get_compiled_qasm(qobj,'qft3')
print("\nQASM Code needed for real ibmqx4:\n")
print(QASM_source)

# How to run on a real ibmqx4 machine.
if real_run:
    run = Q_program.execute(["qft3"], backend="ibmqx4", coupling_map=ibmqx4_coupling, shots=1024, max_credits=3, wait=10, timeout=240)
    plot_histogram(run.get_counts("qft3"))
